﻿//
// ArtistPhotoWatcher.cs: Watches for photos (JPEG files) added to the 
// library directory.
//
// Copyright 2010 Michael Goethe
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using S9PalCommon;
using S9PalCommon.ArtistLibrary;

namespace LastFMPhotoCrawler
{
    public delegate void ArtistEventHandler(object sender, ArtistEventArgs e);

    /// <summary>
    /// Provides data for the ArtistPhotoAdded event.
    /// </summary>
    public class ArtistEventArgs : EventArgs
    {
        private Artist artist;
   
        public Artist Artist
        {
            get
            {
                return artist;
            }
            set
            {
                artist = value;
            }
        }
    }

    public class ArtistPhotoWatcher
    {
        public event ArtistEventHandler ArtistPhotoAdded;

        delegate void ImportPhotosFromArtistDirectoryDelegate();

        FileSystemWatcher watcher = new FileSystemWatcher();

        public ArtistPhotoWatcher()
        {
            ImportPhotosFromAllArtistsDirectories();
            InitializeFileSystemWatcher();
        }

        private void ImportPhotosFromAllArtistsDirectories()
        {
            SortedList<String, Artist> artists = S9PalCommon.Global.ArtistLibrary.Artists;

            for (int i = 0; i < artists.Count; i++)
                ImportPhotosFromArtistDirectory(artists.ElementAt(i).Value);
        }

        private void InitializeFileSystemWatcher()
        {
            if (!Directory.Exists(S9PalCommon.Global.ArtistLibrary.LibraryDirectory))
                Directory.CreateDirectory(S9PalCommon.Global.ArtistLibrary.LibraryDirectory);

            watcher.Path = S9PalCommon.Global.ArtistLibrary.LibraryDirectory;
            /* Watch for changes in LastAccess and LastWrite times, and
               the renaming of files or directories. */
            watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite
               | NotifyFilters.FileName | NotifyFilters.DirectoryName;

            watcher.Filter = "*.*";
            watcher.IncludeSubdirectories = true;

            // Add event handlers.
            watcher.Changed += new FileSystemEventHandler(FileSystemWatcher_OnChanged);
            watcher.Created += new FileSystemEventHandler(FileSystemWatcher_OnChanged);

            // Begin watching.
            watcher.EnableRaisingEvents = true;
        }

        private void ImportPhotosFromArtistDirectory(Artist artist)
        {
            String[] files = null;
            String artistDirectory = S9PalCommon.Global.ArtistLibrary.LibraryDirectory + "\\" + S9PalCommon.Helper.RemoveInvalidFileNameCharacters(artist.Name);

            // This is much faster than a try/catch. The added speed is particularly important if the
            // user has (for some reason or another) deleted their entire library folder.
            if (Directory.Exists(artistDirectory))
            {
                files = S9PalCommon.Helper.GetFilesWithMultiplePatterns(artistDirectory, "*.jpg;*.jpeg", SearchOption.TopDirectoryOnly);
            }
            else
            {
                // If the directory wasn't found, the photos aren't there either, so get rid of them.
                artist.Photos.Clear();
                return;
            }

            for (int i = 0; i < files.Length; i++)
            {
                String photoFilePath = files[i];
                String photoFileName = Path.GetFileName(files[i]);
                bool alreadyInLibrary = false;

                // Check if the photo is already in the main library.
                foreach (ArtistPhoto p in artist.Photos)
                {
                    if (p.LocalFileName.CompareTo(photoFileName) == 0)
                    {
                        alreadyInLibrary = true;
                        break;
                    }
                }

                if (!alreadyInLibrary)
                {
                    ArtistPhoto photo = new ArtistPhoto();
                    int numberOfSyncPhotos = 0;

                    photo.LocalFileName = photoFileName;
                    photo.SourceFileID = Path.GetFileNameWithoutExtension(photoFileName);

                    // Count number of synced photos, so the photo can be inserted at the end of the SYNCED photos.
                    for (int j = 0; j < artist.Photos.Count; j++)
                    {
                        if (artist.Photos[j].SyncPhoto)
                            numberOfSyncPhotos++;
                    }

                    // Insert the photo (add it to the end of the synced photos), and raise a corresponding event.
                    artist.Photos.Insert(numberOfSyncPhotos, photo);

                    // Fire up a new thread to perform the job.
                    ThreadStart threadStarter = delegate { RaiseArtistPhotoAddedWhenComplete(new FileInfo(photoFilePath), artist); };
                    Thread thread = new Thread(threadStarter);
                    thread.IsBackground = true;
                    thread.Start();
                }
            }
        }

        private void ImportPhotosFromArtistDirectory(String directory)
        {
            Artist artist = null;
            String directoryName;

            try
            {
                directoryName = directory.Substring(directory.LastIndexOf(Path.DirectorySeparatorChar) + 1);
            }
            catch (IndexOutOfRangeException)
            {
                return;
            }

            try
            {
                artist = S9PalCommon.Global.ArtistLibrary.Artists[directoryName];
            }
            catch (KeyNotFoundException)
            {
                SortedList<String, Artist> artists = S9PalCommon.Global.ArtistLibrary.Artists;

                for (int i = 0; i < artists.Count; i++)
                {
                    if (S9PalCommon.Helper.RemoveInvalidFileNameCharacters(artists.ElementAt(i).Key).CompareTo(directoryName) == 0)
                    {
                        artist = artists.ElementAt(i).Value;
                        break;
                    }
                }
            }

            if (artist != null)
                ImportPhotosFromArtistDirectory(artist);
        }

        private void FileSystemWatcher_OnChanged(object sender, FileSystemEventArgs e)
        {
            if (Path.GetDirectoryName(Path.GetDirectoryName(e.FullPath)).CompareTo(S9PalCommon.Global.ArtistLibrary.LibraryDirectory) == 0)
                ImportPhotosFromArtistDirectory(Path.GetDirectoryName(e.FullPath));
        }

        private void OnArtistPhotoAdded(Artist artist)
        {
            // Pass in the artist and raise the ArtistPhotoAdded event.
            ArtistEventHandler handler = ArtistPhotoAdded;
            if (handler != null)
            {
                ArtistEventArgs e = new ArtistEventArgs();
                e.Artist = artist;
                handler(this, e);
            }
        }

        private void RaiseArtistPhotoAddedWhenComplete(FileInfo file, Artist artist)
        {
            while (IsFileLocked(file))
                Thread.Sleep(100);

            OnArtistPhotoAdded(artist);
        }

        private bool IsFileLocked(FileInfo file)
        {
            FileStream stream = null;

            try
            {
                stream = file.Open(FileMode.Open, FileAccess.Read);
            }
            catch (IOException)
            {
                return true;
            }
            finally
            {
                if (stream != null)
                    stream.Close();
            }

            return false;
        } 

    }
}
